{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.1 初识函数模板"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模板"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <typename T>  // 定义一个模板，T是一个通用的类型参数\n",
    "T max(T a, T b)        // 定义一个函数模板，名为max，接受两个类型为T的参数a和b\n",
    "{\n",
    "    // if b < a then yield a else yield b\n",
    "    return b < a ? a : b;  // 使用三元运算符返回较大的值\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> 根据 [StepanovNotes]，`max()`模板有意地返回 `b < a ? a: b`，而不是 `a < b ? b: a`，是为了确保函数行为的正确性。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> AI: \n",
    "> - 对于相等元素，它保证返回第一个参数(a)而不是第二个，这在某些应用场景中可能更符合预期\n",
    "> - 当比较对象具有特殊行为时(例如NaN值比较)，这种实现方式可能能更好地处理边缘情况\n",
    "> - 有些类型可能对\"小于\"运算符有优化，而对\"大于\"运算符没有同等优化"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "参数类型为模板参数 `T`，模板参数必须使用以下形式的进行声明:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template < comma-separated-list-of-parameters >"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "示例中，参数列表是 `typename T`。注意 `<` 和 `>` 尖括号。关键字 `typename` 引入了一个类型参数。这是 C++ 程序中最常见的模板参数类型，也可以使用其他参数，会在后面进行讨论 (参见第 3 章)。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "类型参数是`T`，也可以使用其他标识符作为参数名 (`T` 是惯例罢了)。类型参数表示在调用函数时才确定的类型，可以使用支持模板使用操作的类型 (基本类型、类等)。因为 `a` 和 `b` 使用小于操作符进行比较，所以类型 `T` 必须支持小于操作符。`max()`的定义可能不太明显，类型 `T` 的值必须可复制，以便返回。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> C++17 之前，类型 `T` 也必须可复制才能传递参数。C++17 以后，即使复制构造函数和移动构造函数都无效，也可以传递临时变量 (右值，参见附录 B)。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "由于一些历史原因，还可以使用关键字 `class` 来定义类型参数。关键字 `typename` 在 C++98 标准中出现得较晚，所以在此之前，关键字 `class` 是引入类型参数的唯一方法，现在这种方法仍然有效。因此，模板 max() 可以等价地定义为:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <class T>\n",
    "T max(T a, T b)\n",
    "{\n",
    "    return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "即使使用 `class`，模板参数也可以使用任意类型。但这样使用 `class` 可能会引起误会 (不仅是类可以替换为 `T`)，所以更推荐使用 `typename`。但与类声明不同，在声明类型参数时，不能使用关键字`struct`来代替 `typename`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 使用模板"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "max(7, i): 42\n"
     ]
    }
   ],
   "source": [
    "#include <iostream>\n",
    "#include <string>\n",
    "\n",
    "{\n",
    "  int i = 42;\n",
    "  std::cout << \"max(7, i): \" << ::max(7,i) << '\\n';\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "max(f1, f2): 3.4\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  double f1 = 3.4;\n",
    "  double f2 = -6.7;\n",
    "  std::cout << \"max(f1, f2): \" << ::max(f1,f2) << '\\n';\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "max(s1, s2): mathematics\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  std::string s1 = \"mathematics\";\n",
    "  std::string s2 = \"math\";\n",
    "  std::cout << \"max(s1, s2): \" << ::max(s1, s2) << '\\n';\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "代码中，对 `max()` 的使用都用`::` 限定。这是为了确保在全局命名空间中找到 `max()` 模板。因为标准库中有一个 `std::max()`，在某些情况下可以使用，但这里可能会产生歧义。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "模板不会编译成处理任意类型的实体。相反，对于模板所使用的每个类型，会根据模板中生成不同的实体。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "因此，`max()` 对这三种类型进行了分别编译。\n",
    "\n",
    "例如，`max()` 的第一次调用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "{\n",
    "  int i = 42;\n",
    "  std::cout << \"max(7, i): \" << ::max(7,i) << '\\n';\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用 `int` 作为模板形参 `T` 的函数模板。因此，等同于调用的如下函数:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "int max (int a, int b) {\n",
    "  return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "用具体类型替换模板参数的过程称为*实例化*，会产生一个模板实例。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> 面向对象编程中，术语实例 (instance) 和实例化 (instantiate) 用于不同的上下文——类的具体对象。本书针对模板，仅用这两个术语表示对模板的“使用”，除非另有说明。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意，使用函数模板就可以进行实例化，开发者无需单独实例化。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "类似地，`max()` 的其他调用实例化了 `double` 和 `std::string` 的 `max` 模板:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "double max (double, double);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "std::string max (std::string, std::string);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果结果代码有效，`void` 也是有效的模板参数。例如:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template<typename T> \n",
    "T foo(T*) {}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "void* vp = nullptr;\n",
    "foo(vp); // OK: deduces void foo(void*)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 两阶段翻译"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "若为不支持使用操作的类型实例化模板，将导致编译时错误。例如:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <complex>\n",
    "\n",
    "std::complex<float> c1, c2; // doesn’t provide operator <"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "::max(c1,c2); // ERROR at compile time"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "因此，模板“编译”分为两个阶段:\n",
    "\n",
    "1. 若在定义时不进行实例化，则会检查模板代码的正确性，而忽略模板参数。这包括:\n",
    "- 现语法错误，比如: 缺少分号。\n",
    "- 使用未知名称的模板参数 (类型名、函数名……)。\n",
    "- 检查 (不依赖于模板参数) 静态断言。\n",
    "\n",
    "2. 实例化时，再次检查模板代码，确保生成的代码有效。特别是所有依赖于模板参数的部分，都会进行重复检查。例如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template<typename T>\n",
    "void foo(T t)\n",
    "{\n",
    "    undeclared(); // 若undeclared()未知，则在第一阶段编译时报错\n",
    "    undeclared(t); // 若undeclared(T)未知，则在第二阶段编译时报错\n",
    "\n",
    "    static_assert(sizeof(int) > 10,  \"int too small\"); // 若sizeof(int) <= 10，始终断言失败\n",
    "    static_assert(sizeof(T) > 10,  \"T too small\"); // 若示例T的大小小于等于10，则断言失败\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// https://godbolt.org/z/Tc6joWfvh\n",
    "// 第一阶段：模板定义时的检查\n",
    "\n",
    "#include <iostream>\n",
    "#include <type_traits>\n",
    "\n",
    "template <typename T>\n",
    "void foo(T t) {\n",
    "    undeclared(); // 错误：`undeclared` 未定义，第一阶段报错\n",
    "    undeclared(t); // 不报错：依赖于模板参数，延迟到第二阶段检查\n",
    "\n",
    "    static_assert(sizeof(int) > 10, \"int too small\"); // 错误：`sizeof(int)` 永远小于等于 10，第一阶段报错\n",
    "    static_assert(sizeof(T) > 10, \"T too small\"); // 不报错：依赖于模板参数，延迟到第二阶段检查\n",
    "}\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <iostream>\n",
    "#include <type_traits>\n",
    "\n",
    "template <typename T>\n",
    "void foo(T t) {\n",
    "    undeclared(t); // 错误：若 `undeclared(T)` 未定义，则实例化时报错\n",
    "\n",
    "    static_assert(sizeof(T) > 10, \"T too small\"); // 错误：若 `sizeof(T) <= 10`，实例化时断言失败\n",
    "}\n",
    "\n",
    "struct LargeType {\n",
    "    char data[20];\n",
    "};\n",
    "\n",
    "struct SmallType {\n",
    "    char data[5];\n",
    "};\n",
    "\n",
    "int main() {\n",
    "    // foo<int>(42); // 错误：实例化时 sizeof(int) <= 10，断言失败\n",
    "    foo<LargeType>(LargeType{}); // 正确：sizeof(LargeType) > 10\n",
    "    // foo<SmallType>(SmallType{}); // 错误：实例化时 sizeof(SmallType) <= 10，断言失败\n",
    "\n",
    "    return 0;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "检查两次名称的情况称为两阶段查找，在第 14.3.1 节会进行详细讨论。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意，有些编译器不在第一阶段编译时进行完全的检查。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "因此，在模板代码第一次实例化前，可能不会看到已存在的问题。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**编译和连接**\n",
    "\n",
    "两阶段翻译在实际处理模板时，会有一个问题: 当以数模板实例化的方式使用函数模板时，编译器 (在某些时候) 需要查看该模板的定义。当函数的声明可以编译，并连接通过时，就打破了普通函数的编译和链接方式。第 9 章讨论了处理这个问题的方法。现在，我们使用最简单的方法: 仅在头文件中实现每个模板。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.2 模板参数推导"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "当使用函数模板 (如 `max()`) 时，模板参数由传入的参数决定。如果类型 `T` 传递两个 `int` 型参数，那编译器就会认为 `T` 是 `int` 型。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "然而，`T` 可能只是类型的“一部分”。\n",
    "\n",
    "若声明 `max()` 使用常量引用:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <typename T>\n",
    "T max(T const& a, T const& b) {\n",
    "  return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "并传递 `int`，同样 `T` 会推导为 `int`，因为函数形参匹配的是 `int const&`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**类型推导时的类型转换**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意，自动类型转换在类型推导时有一些限制:\n",
    "- 当通过引用声明参数时，简单的转换也不适用于类型推导。用同一个模板参数 `T` 声明的两个实参必须完全匹配。\n",
    "- 当按值声明参数时，只支持简单的转换: 忽略 `const` 或 `volatile` 的限定符，引用转换为引用的类型，原始数组或函数转换为相应的指针类型。对于使用相同模板参数 `T` 声明的两个参数，转换类型必须匹配。\n",
    "\n",
    "例如:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <typename T>\n",
    "T max(T a, T b) {\n",
    "  return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "{\n",
    "  int i = 17;\n",
    "  int const c = 42;\n",
    "  max(i, c); // OK: T推导为int\n",
    "  max(c, c); // OK: T推导为int\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "{\n",
    "  int i = 17;\n",
    "  int& ir = i;\n",
    "  max(i, ir); // OK: T推导为int\n",
    "  int arr[4];\n",
    "  max(&i, arr); // OK: T推导为int*\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面就是反面案例了："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "input_line_13:3:3: error: no matching function for call to 'max'\n",
      "  max(4, 7.2); // ERROR: T推导为int或double\n",
      "  ^~~\n",
      "input_line_7:2:3: note: candidate template ignored: deduced conflicting types for parameter 'T' ('int' vs. 'double')\n",
      "T max(T a, T b) {\n",
      "  ^\n"
     ]
    },
    {
     "ename": "Interpreter Error",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "Interpreter Error: "
     ]
    }
   ],
   "source": [
    "{\n",
    "  max(4, 7.2); // ERROR: T推导为int或double\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "input_line_9:4:3: error: no matching function for call to 'max'\n",
      "  max(\"hello\", s); // ERROR: T推导为char const*或std::string\n",
      "  ^~~\n",
      "input_line_7:2:3: note: candidate template ignored: deduced conflicting types for parameter 'T' ('const char *' vs. 'std::__cxx11::basic_string<char>')\n",
      "T max(T a, T b) {\n",
      "  ^\n",
      "/usr/include/c++/11/bits/stl_algobase.h:254:5: note: candidate template ignored: deduced conflicting types for parameter '_Tp' ('char [6]' vs. 'std::__cxx11::basic_string<char>')\n",
      "    max(const _Tp& __a, const _Tp& __b)\n",
      "    ^\n",
      "/usr/include/c++/11/bits/stl_algobase.h:300:5: note: candidate function template not viable: requires 3 arguments, but 2 were provided\n",
      "    max(const _Tp& __a, const _Tp& __b, _Compare __comp)\n",
      "    ^\n"
     ]
    },
    {
     "ename": "Interpreter Error",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "Interpreter Error: "
     ]
    }
   ],
   "source": [
    "#include <string>\n",
    "\n",
    "{\n",
    "  std::string s;\n",
    "  max(\"hello\", s); // ERROR: T推导为char const*或std::string\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "有三种方法可以处理此类错误:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 1. 强制转换:\n",
    "\n",
    "{\n",
    "  max(static_cast<double>(4), 7.2); // OK\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 2. 显式指定 (或限定)T 的类型\n",
    "\n",
    "{\n",
    "  max<double>(4, 7.2); // OK\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. 指定参数可以有不同的类型。\n",
    "\n",
    "第 1.3 节将进行详细的说明。第 7.2 节和第 15 章将详细讨论类型推导时的类型转换规则。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**默认参数的类型推导**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "类型推导不适用于默认调用参数。例如:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <typename T>\n",
    "void f(T = \"\") {}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "{\n",
    "  f(1); // OK: 推导出T为int，因此其可以调用f<int>(1)\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "input_line_9:3:3: error: no matching function for call to 'f'\n",
      "  f(); // ERROR: 不能推断出T的类型\n",
      "  ^\n",
      "input_line_7:2:6: note: candidate template ignored: couldn't infer template argument 'T'\n",
      "void f(T = \"\") {}\n",
      "     ^\n"
     ]
    },
    {
     "ename": "Interpreter Error",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "Interpreter Error: "
     ]
    }
   ],
   "source": [
    "{\n",
    "  f(); // ERROR: 不能推断出T的类型\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为了支持这种情况，必须为模板形参声明一个默认实参，这将在 1.4 节中进行详述:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <string>\n",
    "\n",
    "template <typename T = std::string>\n",
    "void f(T = \"\") {}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "{\n",
    "  f(); // ok\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.3 多模板参数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "函数模板可以有两组不同的类型参数:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 模板参数，用尖括号声明在函数模板名之前\n",
    "\n",
    "template <typename T> // T是模板参数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 调用参数，在函数模板名称后面的圆括号中声明:\n",
    "\n",
    "T max (T a, T b) // a和b调用参数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "其实，可以有任意多种模板参数。可以为两种不同类型的调用参数定义 `max()` 模板:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <typename T1, typename T2>\n",
    "T1 max(T1 a, T2 b) {\n",
    "  return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "{\n",
    "  auto m = ::max(4, 7.2); // OK, 不过返回类型与第一个参数类型一样\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "将不同类型的参数传递给 `max()` 模板这会引发一个问题。若使用其中一种形参类型作为返回类型，则其他参数可能需要向这种类型进行转换。因此，返回类型取决于调用参数的顺序。`66.66` 和`42` 的最大值将是 `double` 类型的 `66.66`，而 `42` 和 `66.66` 的最大值将是 `int` 类型的 `66`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "C++ 提供了不同的方法来处理这个问题:\n",
    "- 为返回类型引入第三个模板参数\n",
    "- 让编译器找出返回类型。\n",
    "- 将返回类型声明为两个参数类型的“公共类型”。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 返回类型的模板参数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "模板参数推导可以使用普通调用函数的语法使用函数模板，所以可以不用显式地指定与模板参数对应的类型。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "当然，也可以显式地指定模板参数使用的类型:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <typename T>\n",
    "T max(T a, T b) {\n",
    "  return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "{\n",
    "  ::max<double>(4, 7.2); // 示例中T是double\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "若模板和调用参数之间没有关系，并且无法确定模板参数，则必须显式指定模板参数。\n",
    "\n",
    "例如，可以引入第三个模板参数类型来指定函数模板的返回类型:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <typename T1, typename T2, typename RT>\n",
    "RT max(T1 a, T1 b) {\n",
    "  return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "然而，模板参数不会去推导返回类型，"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> 推导可以视为重载解析的一部分——这个过程不基于返回类型。唯一的例外是转换操作符的返回类型。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`RT` 不会出现在函数调用参数的类型中。因此，不能推导出 `RT` 的类型。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> C++ 中，不可从调用代码的上下文中推断返回类型。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "因此，必须显式地指定模板参数。例如:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "input_line_10:3:33: warning: implicit conversion from 'double' to 'int' changes value from 7.2 to 7 [-Wliteral-conversion]\n",
      "  ::max<int, double, double>(4, 7.2); // OK, 但很无趣\n",
      "  ~~                            ^~~\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  ::max<int, double, double>(4, 7.2); // OK, 但很无趣\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "了解了显式地指定所有函数模板参数，或没有函数模板参数的情况。另一种方法是只显式指定第一个参数，并允许演绎过程派生其他参数。通常，必须指定最后一个不能隐式确定的参数类型之前的所有参数类型。因此，若在例子中改变了模板参数的顺序，只需要指定返回类型即可:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <typename RT, typename T1, typename T2>\n",
    "RT max(T1 a, T2 b) {\n",
    "  return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "{\n",
    "  ::max<double>(4, 7.2); // OK: 返回类型是double，T1和T2可以进行推演\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "例子中，对 `max<double>` 的调用显式地将 `RT` 设置为 `double`，但是参数 `T1` 和 `T2` 根据实参推导为 `int` 和 `double`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意，`max()` 的这些版本也没什么特别。单参数版本时，若传递了两个不同类型的参数，则可以指定参数 (和返回) 类型。因此，使用 `max()` 单参数版本就好。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "关于推导过程的详细信息，请参见第 15 章。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 推导返回类型"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "若返回类型依赖于模板参数，最好和简单的方法是让编译器判断返回类型。从 C++14 开始，可以不显式声明返回类型 (需要声明返回类型为 `auto`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <typename T1, typename T2>\n",
    "auto max(T1 a, T2 b) {\n",
    "  return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "返回类型中使用 `auto`，而没有相应的尾部返回类型 (`->`) 表明需要从函数体中的 `return` 语句推导出实际的返回类型。\n",
    "\n",
    "当然，从函数体推断返回类型是可行的。因此，函数体代码必须有效，有多个返回语句时也必须匹配。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "C++14 之前，只能让编译器通过将函数的实现作为其声明的一部分来确定返回类型。\n",
    "\n",
    "C++11 中，尾部的返回类型允许使用调用时的参数，声明的返回类型是从三元操作符某个分支派生的："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <typename T1, typename T2>\n",
    "auto max(T1 a, T2 b) -> decltype(b < a ? a : b) {\n",
    "  return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这有些复杂，返回类型由三元操作符决定，但会产生简答的结果 (如果 a 和 b 是不同的类型，则会为返回值确定一个通用的类型)。\n",
    "\n",
    "注意："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template<typename T1, typename T2>\n",
    "auto max(T1 a, T2 b) -> decltype(b < a ? a : b);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "是一个声明，使编译器使用三元操作符，可以在编译时找出 `max()` 的返回类型，实现也不一定要匹配。\n",
    "\n",
    "事实上，使用 `true` 作为操作符的条件就够了:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template<typename T1, typename T2>\n",
    "auto max(T1 a, T2 b) -> decltype(true ? a : b);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "然而，这个定义有一个缺陷: 返回类型可能是引用类型，在某些条件下 `T` 可能是引用。\n",
    "\n",
    "因为这个原因，应该返回 `T` 衰变的类型:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <type_traits>\n",
    "\n",
    "template <typename T1, typename T2>\n",
    "auto max(T1 a, T2 b) -> typename std::decay<decltype(b < a ? a : b)>::type {\n",
    "  return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里使用了 `std::decay<>`，修饰了返回值的类型。其在标准库中的 `<type_traits>` 头文件中定义(参见第 D.4 节)。因为是类型，必须用 `typename` 进行限定才能访问 (参见第 5.1 节)。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`auto` 类型的初始化是衰变的，这也适用于返回类型为 `auto` 的返回值。`auto` 作为返回类型的行为就像下面的代码一样，其中 `a` 由 `i`(衰变类型) 声明:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "{\n",
    "  int i = 42;\n",
    "  int const& ir = i; // ir引用于i\n",
    "  auto a = ir; // a的类型为int\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 返回类型为公共类型"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "C++11 后，标准库提供了一种方法来指定选择“公共类型”。`std::common_type<>::type` 会产生由两个 (或多个) 不同类型作为模板类型的“公共类型”。\n",
    "\n",
    "例如:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <type_traits>\n",
    "\n",
    "template <typename T1, typename T2>\n",
    "typename std::common_type<T1, T2>::type max(T1 a, T2 b) {\n",
    "  return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <type_traits>\n",
    "\n",
    "template <typename T1, typename T2>\n",
    "std::common_type_t<T1, T2> max(T1 a, T2 b) {\n",
    "  return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`std::common_type` 在 `<type_traits>` 头文件中定义，其生成一个具有返回类型的结构体。\n",
    "\n",
    "其核心用法如下所示:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "typename std::common_type<T1, T2>::type // since C++11"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "C++14 起，可以通过在特性名称后面添加`_t` 跳过 `typename` 和`::type` 来简化 `trait` 的使用 (详见第2.8 节)，这样返回类型定义就变成了:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "std::common_type_t<T1, T2> // C++14起可用"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`std::common_type<>` 使用了一些模板编程技巧，会在第 26.5.2 节中讨论。内部根据特定类型的三元操作符或特化的语言规则选择结果类型。因此，`::max(4, 7.2)` 和`::max(7.2, 4)` 都返回了相同的`double` 类型值 `7.2`。注意 `std::common_type<>` 也会衰变，详见 D.5 节。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.4 默认模板参数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "还可以为模板参数定义默认值。这些值称为默认模板参数，可以用于任何类型的模板。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> C++11 前，因为历史原因，默认模板参数只允许在类模板中使用。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "甚至可能引用前面的模板参数。\n",
    "\n",
    "若希望定义返回类型的方法与具有多个参数类型的能力结合起来 (如前一节所述)，那么可以为返回类型引入模板参数 `RT`，将两个参数的公共类型作为默认类型。\n",
    "\n",
    "同样，有多种选择:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. 可以直接使用三元操作符。但在使用三元操作符之前，只能使用参数的类型:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <type_traits>\n",
    "\n",
    "template <typename T1, typename T2, typename RT = std::decay_t<decltype(true ? T1() : T2())>>\n",
    "RT max(T1 a, T2 b) {\n",
    "  return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意 `std::decay_t<>` 的用法以确保返回的不是引用类型。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> C++11 中，必须使用 `typename std::decay<…>::type`，而非 `std::decay_t<…>`(见第 2.8节)。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "此实现要求能够为传递的类型调用默认构造函数。还有另一种解决方案，使用 `std::declval`，但是这会使声明更加复杂。参见第 11.2.3 节中的示例。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 也可以使用 `std::common_type<>` 来指定返回类型的默认值:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <type_traits>\n",
    "\n",
    "template <typename T1, typename T2, typename RT = std::common_type_t<T1, T2>>\n",
    "RT max(T1 a, T2 b) {\n",
    "  return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意 `std::common_type<>` 的衰变，因此返回值不会是引用。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以使用返回类型的默认值:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "{\n",
    "  auto a = ::max(4, 7.2);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "或者显式地指定返回的类型:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "{\n",
    "  auto b = ::max<double, int, long double>(7.2, 4);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "不过，目前必须指定三种类型才能只明确指定返回类型。所以，将返回类型移动到第一个模板参数，就可以对其类型进行推导。原则上，即使后面没有默认参数，推导的函数模板参数也可以有默认类型:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "template <typename RT = long, typename T1, typename T2>\n",
    "RT max(T1 a, T2 b) {\n",
    "  return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通过定义，可以这样使用:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "{\n",
    "  int i;\n",
    "  long l;\n",
    "\n",
    "  ::max(i, l); // 返回long(返回类型的模板默认类型)\n",
    "  ::max<int>(4, 42); // 显式返回int\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "但这种方法只在模板参数有默认值时才有意义。这里，需要模板参数的默认值依赖于之前的模板参数。原理上这可行 (在 26.5.1 节中会进行讨论)，但是这种技术依赖于类型特征，会使定义复杂化。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "由于所有这些原因，最好和最简单的解决方案是让编译器推导出第 1.3.2 节中提出的返回类型。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.5. 重载函数模板"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "与普通函数一样，函数模板也可以重载。可以使用相同的函数名来声明不同的函数体，当使用该函数名称时，编译器会决定调用哪一个候选函数。即使没有模板，这个决策规则也会相当复杂。本节中，将讨论涉及模板的重载。如果不熟悉无模板重载的基本规则，请参阅附录 C，其中提供了关于重载解析规则相当详细的介绍。\n",
    "\n",
    "下面的程序示例演示了如何重载函数模板:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <iostream>\n",
    "\n",
    "// maximum of two int values:\n",
    "int max (int a, int b)\n",
    "{   \n",
    "    std::cout << \"maximum of two int values:\\n\";\n",
    "    return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <iostream>\n",
    "\n",
    "// maximum of two values of any type:\n",
    "template<typename T>\n",
    "T max (T a, T b)\n",
    "{ \n",
    "    std::cout << \"maximum of two values of any type:\\n\";\n",
    "    return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "maximum of two int values:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  ::max(7, 42); // calls the non-template for two ints\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "maximum of two values of any type:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  ::max(7.0, 42.0); // calls max<double> (by argument deduction)\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "maximum of two values of any type:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  ::max('a', 'b'); // calls max<char> (by argument deduction)\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "maximum of two values of any type:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  ::max<double>(7, 42); // calls max<double> (no argument deduction)\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "input_line_15:3:14: warning: implicit conversion from 'double' to 'int' changes value from 42.7 to 42 [-Wliteral-conversion]\n",
      "  ::max('a', 42.7); // calls the non-template for two ints\n",
      "  ~~         ^~~~\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "maximum of two int values:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  ::max('a', 42.7); // calls the non-template for two ints\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "非模板函数可以与相同名称和相同类型的函数模板共存。其他相同的情况下，重载解析将优先使用非模板方式。\n",
    "\n",
    "第一个调用验证了这个规则:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "maximum of two int values:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  ::max(7, 42); // calls the non-template for two ints\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "若模板可以生成匹配更好的函数实例，则选择模板。\n",
    "\n",
    "这在 `max()` 的第二次和第三次调用中得到了验证:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "maximum of two values of any type:\n",
      "maximum of two values of any type:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  ::max(7.0, 42.0); // calls max<double> (by argument deduction)\n",
    "  ::max('a', 'b'); // calls max<char> (by argument deduction)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里，因为不需要将 `double` 或 `char` 转换成 `int`(参见 C.2 节的重载解析规则)，模板匹的更好。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "也可以显式指定一个空的模板参数列表。这种语法表明只有模板才能解析调用，但所有模板参数都应该根据调用参数进行推导:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "maximum of two values of any type:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  ::max<double>(7, 42); // calls max<double> (no argument deduction)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "对推导的模板参数不进行自动类型转换，从而会自动的对普通函数参数进行类型转换，因此最后一次调用使用了非模板函数 (`’a’` 和 `42.7` 都转换为 `int`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "input_line_14:3:14: warning: implicit conversion from 'double' to 'int' changes value from 42.7 to 42 [-Wliteral-conversion]\n",
      "  ::max('a', 42.7); // calls the non-template for two ints\n",
      "  ~~         ^~~~\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "maximum of two int values:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  ::max('a', 42.7); // calls the non-template for two ints\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "一个有趣的例子是重载 `maximum` 模板，其只能显式指定返回类型:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <iostream>\n",
    "\n",
    "template<typename T1, typename T2>\n",
    "auto max (T1 a, T2 b)\n",
    "{\n",
    "    std::cout << \"auto:\\n\";\n",
    "    return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <iostream>\n",
    "\n",
    "template<typename RT, typename T1, typename T2>\n",
    "RT max (T1 a, T2 b)\n",
    "{\n",
    "    std::cout << \"RT:\\n\";\n",
    "    return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在，可以调用 `max()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "auto:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  auto a = ::max(4, 7.2); // 使用第一个函数模板\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "RT:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  auto b = ::max<long double>(7.2, 4); // 使用第二个函数模板\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "当这样使用时:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "input_line_13:3:12: error: call to 'max' is ambiguous\n",
      "  auto c = ::max<int>(4, 7.2); // ERROR: 两个函数模板都可以匹配\n",
      "           ^~~~~~~~~~\n",
      "input_line_8:2:6: note: candidate function [with T1 = int, T2 = double]\n",
      "auto max (T1 a, T2 b)\n",
      "     ^\n",
      "input_line_10:2:4: note: candidate function [with RT = int, T1 = int, T2 = double]\n",
      "RT max (T1 a, T2 b)\n",
      "   ^\n"
     ]
    },
    {
     "ename": "Interpreter Error",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "Interpreter Error: "
     ]
    }
   ],
   "source": [
    "{\n",
    "  auto c = ::max<int>(4, 7.2); // ERROR: 两个函数模板都可以匹配\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这两个模板都匹配，这导致重载解析过程出现歧义。因此，重载函数模板时，应该确保只有一个函数模板与调用匹配。\n",
    "\n",
    "例子是重载指针和普通 C 字符串的最大值比较函数模板:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <iostream>\n",
    "\n",
    "template<typename T>\n",
    "auto max(T a, T b)\n",
    "{\n",
    "    std::cout << \"任意类型的两个最大值:\\n\";\n",
    "    return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <iostream>\n",
    "\n",
    "template<typename T>\n",
    "auto max(T* a, T* b)\n",
    "{\n",
    "    std::cout << \"两个指针内容的最大值:\\n\";\n",
    "    return *b < *a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "任意类型的两个最大值:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  int a = 7;\n",
    "  int b = 42;\n",
    "  auto m1 = ::max(a, b); // max():两个int类型的值\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "任意类型的两个最大值:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  std::string s1 = \"hey\";\n",
    "  std::string s2 = \"you\";\n",
    "  auto m2 = ::max(s1, s2); // max():两个std::string类型的值\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "两个指针内容的最大值:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  int a = 7;\n",
    "  int b = 42;\n",
    "  int* p1 = &b;\n",
    "  int* p2 = &a;\n",
    "  auto m3 = ::max(p1, p2); // max():两个指针类型的值\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "任意类型的两个最大值:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  int a = 7;\n",
    "  int b = 42;\n",
    "  int* p1 = &b;\n",
    "  int* p2 = &a;\n",
    "  auto m3 = ::max(p1, p2); // max():两个指针类型的值\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// https://godbolt.org/z/an5Pcvhxx\n",
    "\n",
    "#include <iostream>\n",
    "#include <cstring>\n",
    "\n",
    "// char const* max(char const* a, char const* b)\n",
    "auto max(char const* a, char const* b)\n",
    "{\n",
    "    std::cout << \"两个C字符串的最大值:\\n\";\n",
    "    return *b < *a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "两个C字符串的最大值:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  char const* x = \"hello\";\n",
    "  char const* y = \"world\";\n",
    "  auto m4 = ::max(x, y); // max():两个C字符串类型的值\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意，`max()` 的所有重载中，参数都是通过值传递的。重载函数模板时，最好不要进行不必要的更改，可以将更改限制在参数的数量或显式地指定模板参数。否则，可能会出现意想不到的后果。若实现了 `max()` 模板，通过引用传递参数，对两个通过值传递 C 字符串进行重载实现，从而就不能使用三个参数的版本来比较三个 C 字符串:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// https://godbolt.org/z/an5Pcvhxx\n",
    "\n",
    "#include <cstring>\n",
    "\n",
    "// maximum of two values of any type (call-by-reference)\n",
    "template <typename T>\n",
    "T const& max(T const& a, T const& b) {\n",
    "    return b < a ? a : b;\n",
    "}\n",
    "\n",
    "// maximum of two C-strings (call-by-value)\n",
    "char const* max(char const* a, char const* b) {\n",
    "    return std::strcmp(b, a) < 0 ? a : b;\n",
    "}\n",
    "\n",
    "// maximum of three values of any type (call-by-reference)\n",
    "template <typename T>\n",
    "T const& max(T const& a, T const& b, T const& c) {\n",
    "    return max(max(a, b), c);  // error if max(a,b) uses call-by-value\n",
    "}\n",
    "\n",
    "int main() {\n",
    "    auto m1 = ::max(7, 42, 68);  // OK\n",
    "\n",
    "    char const* s1 = \"frederic\";\n",
    "    char const* s2 = \"anica\";\n",
    "    char const* s3 = \"lucas\";\n",
    "    // auto m2 = ::max(s1, s2, s3);  // run-time ERROR\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// maximum of two values of any type (call-by-reference)\n",
    "template <typename T>\n",
    "T const& max(T const& a, T const& b) {\n",
    "    return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <cstring>\n",
    "\n",
    "// maximum of two C-strings (call-by-value)\n",
    "auto max(char const* a, char const* b) {\n",
    "    return std::strcmp(b, a) < 0 ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// maximum of three values of any type (call-by-reference)\n",
    "template <typename T>\n",
    "T const& max(T const& a, T const& b, T const& c) {\n",
    "    return max(max(a, b), c);  // error if max(a,b) uses call-by-value\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "{\n",
    "  auto m1 = ::max(7, 42, 68); // OK\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "input_line_11:4:12: warning: returning reference to local temporary object [-Wreturn-stack-address]\n",
      "    return max(max(a, b), c);  // error if max(a,b) uses call-by-value\n",
      "           ^~~~~~~~~~~~~~~~~\n",
      "input_line_13:6:15: note: in instantiation of function template specialization 'max<const char *>' requested here\n",
      "  auto m2 = ::max(s1, s2, s3); // 运行时错误（未定义的行为）\n",
      "              ^\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  char const* s1 = \"frederic\";\n",
    "  char const* s2 = \"anica\";\n",
    "  char const* s3 = \"lucas\";\n",
    "\n",
    "  auto m2 = ::max(s1, s2, s3); // 运行时错误（未定义的行为）\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "问题是，若对三个 C 字符串调用 `max()`，"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "return max (max(a,b), c);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "就会导致运行时错误，因为对于 C 字符，`max(a, b)` 创建了一个新临时变量，并通过引用返回。但这个临时值越过返回语句完成就会销毁，留给 `main()` 一个悬空引用。不幸的是，这个错误非常微妙，可能不必现。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> 一般来说，符合标准的编译器可以编译这段代码。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "与此相反，`main()` 中对 `max()` 的第一次调用不会出现相同的问题。虽为参数 (`7`、`42` 和 `68`)创建了临时变量，但这些临时变量在 `main()` 中创建，`main()`中会一直存在，直到程序执行完毕。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这是由于重载解析规则, 而导致行为与预期不同的代码示例。此外，需要在调用函数之前声明函数的所有重载版本。因为在进行相应的函数调用时，并非所有重载函数都可见 (这很重要)。\n",
    "\n",
    "例如，定义三个参数版本的 `max()`，而没有声明用于 `int` 类型的两个参数版本，就会导致使用三个参数版本的函数时，使用到两个参数的模板:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <iostream>\n",
    "\n",
    "template<typename T>\n",
    "auto max(T a, T b)\n",
    "{\n",
    "    std::cout << \"任意类型的两个最大值:\\n\";\n",
    "    return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <iostream>\n",
    "\n",
    "template<typename T>\n",
    "auto max(T a, T b, T c)\n",
    "{\n",
    "    std::cout << \"三个任意类型的最大值:\\n\";\n",
    "    return max(a, max(b, c)); // 对整型也使用模板版本, 因为下面的声明来了太晚了\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <iostream>\n",
    "\n",
    "int max(int a, int b)\n",
    "{\n",
    "    std::cout << \"两个int值的最大值:\\n\";\n",
    "    return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "三个任意类型的最大值:\n",
      "任意类型的两个最大值:\n",
      "任意类型的两个最大值:\n"
     ]
    }
   ],
   "source": [
    "{\n",
    "  ::max(47, 11, 33); // OOPS: 这里使用的是max<T>()，而非max(int,int)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们将在第 13.2 节中讨论其中细节。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.6. 常见问题"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "即使是这些简单的函数模板示例，也可能引发进一步的问题。有三个常见问题，在这里简单的讨论下。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 使用值，还是引用传递参数?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为什么通常声明函数按值传递参数，而不是使用引用。一般来说，除了简单类型 (比如基本类型或 `std::string_view`)，因为不会创建副本，所以推荐使用引用传递。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "然而，按值传递在下面的情况下会更好:\n",
    "- 语法简单。\n",
    "- 编译器会进行很好的优化。\n",
    "- 移动语义会使复制成本降低。\n",
    "- 没有复制或移动操作。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "此外，对于模板来说:\n",
    "- 模板可能同时用于简单类型和复杂类型，因此为复杂类型选择这种方法时，可能会对简单类型产生反效果。\n",
    "- 作为调用者，可以通过引用来传递参数，可以使用 `std::ref()` 和 `std::cref()`(参见 7.3节)。\n",
    "- 虽然传递字符串字面值或原始数组可能会产生问题，但通过引用传递通常会有更多的问题。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这些将在第 7 章中详细讨论。目前，我们使用值传递参数 (除非某些功能只有在使用引用时才使用引用)。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 为什么不用内联?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通常，函数模板不必使用内联声明。与普通的非内联函数不同，我们可以在头文件中定义非内联函数模板，并在多个翻译单元中包含该头文件。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "该规则的唯一例外是，对特定类型的模板进行完全特化，从而产生的代码不再是泛型 (定义了所有模板参数)。参见 9.2 节了解更多细节。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "从严格的语言定义角度来看，内联意味着函数的定义可以在程序中出现多次。也表示编译器对该函数的调用应该“内联展开”: 某些情况下可以产生更有效的代码，但在许多其他情况下反而会降低代码的效率。现在，编译器通常能够更好地决定是否采纳使用 `inline` 关键字的提示。但是，编译器仍然要考虑在该决策中是否存在内联。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 为什么不用 constexpr?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "C++11 后，可以使用 `constexpr` 提供在编译时计算某些值的能力。对于很多模板来说，这很有意义。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "例如，为了能够在编译时使用 `maximum` 函数，必须声明它:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <iostream>\n",
    "\n",
    "template<typename T1, typename T2>\n",
    "constexpr auto max(T1 a, T2 b)\n",
    "{\n",
    "    return b < a ? a : b;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这样，就可以在有编译时使用 `maximum` 函数模板，比如在声明原始数组的大小时:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "{\n",
    "  int a[::max(sizeof(char),1000u)];\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "或者定义 `std::array<>` 的大小:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "#include <array>\n",
    "\n",
    "std::array<std::string, ::max(sizeof(char), 1000u)> arr;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意，将 `1000` 作为 `unsigned int` 传递是为了避免在模板中比较有符号值和无符号值时，编译器发出的警告。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "第 8.2 节将讨论使用 `constexpr` 的例子。为了让我们的注意力集中在基本特性上，在讨论其他模板特性时，我们通常会跳过 `constexpr`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1.7. 总结"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "C++17",
   "language": "C++17",
   "name": "xcpp17"
  },
  "language_info": {
   "codemirror_mode": "text/x-c++src",
   "file_extension": ".cpp",
   "mimetype": "text/x-c++src",
   "name": "C++17",
   "version": "17"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
